using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace GriffinNav.GPS.NMEA
{
    // Global Positioning System Fix Data
    internal struct GPGGA
    {
        //$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 
        //     GGA          Global Positioning System Fix Data 
        //     123519       Fix taken at 12:35:19 UTC 
        //     4807.038,N   Latitude 48 deg 07.038' N 
        //     01131.000,E  Longitude 11 deg 31.000' E 
        //     1            Fix quality: 0 = invalid 
        //                               1 = GPS fix (SPS) 
        //                               2 = DGPS fix 
        //                               3 = PPS fix 
        //                               4 = Real Time Kinematic 
        //                               5 = Float RTK 
        //                               6 = estimated (dead reckoning) (2.3 feature) 
        //                               7 = Manual input mode 
        //                               8 = Simulation mode 
        //     08           Number of satellites being tracked 
        //     0.9          Horizontal dilution of position 
        //     545.4,M      Altitude, Meters, above mean sea level 
        //     46.9,M       Height of geoid (mean sea level) above WGS84 
        //                      ellipsoid 
        //     (empty field) time in seconds since last DGPS update 
        //     (empty field) DGPS station ID number 
        //     *47          the checksum data, always begins with * 

        internal static GPGGA Empty = new GPGGA(string.Empty);
        internal const string PREFIX = "$GPGGA,";

        private string[] mGgaParams;

        private int mLongitudeDegrees;
        private int mLatitudeDegrees;
        private decimal mLongitudeMinutes;
        private decimal mLatitudeMinutes;
        private LongitudeDirection mLongitudeDirection;
        private LatitudeDirection mLatitudeDirection;

        private Position mPosition;

        internal GPGGA(string ggaString)
        {
            mPosition = new Position();

            mLongitudeDegrees = 0;
            mLatitudeDegrees = 0;
            mLongitudeMinutes = 0M;
            mLatitudeMinutes = 0M;
            mLongitudeDirection = LongitudeDirection.E;
            mLatitudeDirection = LatitudeDirection.N;

            mGgaParams = null;

            // check for a value
            if (!string.IsNullOrEmpty(ggaString))
            {
                // update fix data
                Update(ggaString);
            }
            else
            {
                // create empty position object
                mPosition = new Position();
            }
        }

        internal Position Position
        {
            get
            {
                return mPosition;
            }
        }

        internal void Update(string ggaString)
        {
            if (!string.IsNullOrEmpty(ggaString))
            {
                // remove "$GPGGA," prefix and split sentence
                mGgaParams = ggaString.Remove(0, PREFIX.Length).Split(new char[] { ',' });

                // time of GPS fix
                if (!string.IsNullOrEmpty(mGgaParams[0]))
                {
                    mPosition.FixTime = DateTime.Parse(
                        mGgaParams[0].Substring(0, 2) + ":" + 
                        mGgaParams[0].Substring(2, 2) + ":" +
                        mGgaParams[0].Substring(4, 2)); 
                }

                // latitude parameter
                if (!string.IsNullOrEmpty(mGgaParams[1]))
                {
                    // split into degrees and minutes
                    mLatitudeDegrees = int.Parse(mGgaParams[1].Substring(0, 2));
                    mLatitudeMinutes = decimal.Parse(mGgaParams[1].Substring(2, mGgaParams[1].Length - 2));

                    // latitude direction
                    if (mGgaParams[2].Equals("S"))
                    {
                        // south
                        mLatitudeDirection = LatitudeDirection.S;
                    }
                    else
                    {
                        // north
                        mLatitudeDirection = LatitudeDirection.N;
                    }

                    // update latitude
                    mPosition.Latitude.Update(mLatitudeDegrees, mLatitudeMinutes, mLatitudeDirection);
                }

                // longitude parameter
                if (!string.IsNullOrEmpty(mGgaParams[3]))
                {
                    // split into degrees and minutes
                    mLongitudeDegrees = int.Parse(mGgaParams[3].Substring(0, 3));
                    mLongitudeMinutes = decimal.Parse(mGgaParams[3].Substring(3, mGgaParams[3].Length - 3));

                    // longitude direction
                    if (mGgaParams[4].Equals("W"))
                    {
                        mLongitudeDirection = LongitudeDirection.W;
                    }
                    else
                    {
                        mLongitudeDirection = LongitudeDirection.E;
                    }

                    // update longitude
                    mPosition.Longitude.Update(mLongitudeDegrees, mLongitudeMinutes, mLongitudeDirection);
                }

                // GPS fix quality
                if (!string.IsNullOrEmpty(mGgaParams[5]))
                {
                    mPosition.Fix = (FixQuality)int.Parse(mGgaParams[5]);
                }

                // number of satellites being tracked
                if (!string.IsNullOrEmpty(mGgaParams[6]))
                {
                    mPosition.SatelliteCount = int.Parse(mGgaParams[6]);
                }

                // altitude in meters
                if (!string.IsNullOrEmpty(mGgaParams[8]))
                {
                    mPosition.Altitude.Update(decimal.Parse(mGgaParams[8]));
                }
            }
        }
    }
}
